package parser

import (
	"fmt"
	"github.com/fatih/color"
	"github.com/hashicorp/hcl/v2"
	"github.com/ihaiker/tfwrapper/internal/config"
	"github.com/ihaiker/tfwrapper/internal/errors"
	"github.com/ihaiker/tfwrapper/internal/eval"
	"github.com/ihaiker/tfwrapper/internal/shells"
	"github.com/ihaiker/tfwrapper/internal/util"
	"github.com/sirupsen/logrus"
	"os"
	"path"
	"path/filepath"
)

func parseStacks(options config.Running, file *hcl.File) (*config.Stacks, hcl.Diagnostics) {
	ctx, diags := eval.CreateEvalContext(options)
	if diags.HasErrors() {
		return nil, diags
	}
	stacks := new(config.Stacks)
	if diags = ctx.PartialUniqueBlock(file.Body, "stacks", stacks); diags.HasErrors() {
		return stacks, diags
	}

	configRoot := path.Dir(options.CurrentConfigPath)
	for _, stack := range stacks.Stacks {
		for _, source := range stack.Source {
			sourcePath := path.Join(configRoot, source)
			if !util.Exists(sourcePath) {
				err := fmt.Errorf("stack %s source named %s not exists: %s", stack.Name, source, sourcePath)
				r := stack.Remain.MissingItemRange()
				return nil, errors.SubjectDiagnostics(&r, "Stack source hclFile not found", err)
			}
		}
	}

	if options.Stack != "" && !stacks.Has(options.Stack) {
		return stacks, errors.Diagnostics("Stack not found", fmt.Errorf("stack named %s not found", options.Stack))
	}
	return stacks, nil
}

func checkRunInStackDir(cfg config.TFWrapperConfig) (stackNames []string) {
	configDir := filepath.Dir(cfg.CurrentConfigPath)
	logrus.Debugf("user execute command in %s", cfg.WorkingDir)

	relativePath, _ := filepath.Rel(configDir, cfg.WorkingDir)
	if relativePath != "" && relativePath != "." {
		logrus.Debugf("relative path %s", relativePath)
	}

	stackNames = make([]string, 0)
	for _, stack := range cfg.Stacks.Stacks {
		if util.Contain(stack.Source, relativePath) {
			stackNames = append(stackNames, stack.Name)
		}
	}
	if len(stackNames) == 1 {
		logrus.Info(color.HiYellowString(
			"If you execute it in the stack folder, it will be set to stack by default as: %s", color.HiGreenString(stackNames[0])))
	} else if len(stackNames) > 1 {
		logrus.Warnf("%s %s",
			color.HiYellowString("May be executed under the public folders (%s) of multiple stacks: ", relativePath),
			util.Color(color.FgHiRed, stackNames))
	}
	return
}

//chooseStackName 获取用户选择的stack名字
func chooseStackName(cfg config.TFWrapperConfig) (string, bool, error) {
	//获取用户运行的路径是不是stack路径执行，如果是，则返回stack的名字
	runDirStacks := checkRunInStackDir(cfg)
	runInStackDir := len(runDirStacks) > 0

	//用户已经配置了stack（启用优先级：cli flags, 全局配置文件，tfwrapper配置文件）
	stackName := util.Switch(cfg.Stack, cfg.Stacks.Default)

	//用户已经指定或者配置及使用
	if stackName != "" {
		if len(runDirStacks) > 0 && !util.Contain(runDirStacks, stackName) {
			return "", runInStackDir, fmt.Errorf("the specified stack is inconsistent with"+
				" the stack of the running location! %s <-|-> %s",
				color.HiRedString(stackName), util.Color(color.FgHiRed, runDirStacks))
		}
		return stackName, runInStackDir, nil
	}

	definedStackNames := cfg.Stacks.Names()
	if len(runDirStacks) == 1 { //用户在唯一的stack路径下执行，即是选择当前stack执行
		return runDirStacks[0], runInStackDir, nil
	} else if len(runDirStacks) > 0 {
		definedStackNames = runDirStacks
	}

	var err error
	prompt := fmt.Sprintf("Which stack do you want to execute ? [ %s ] : ", util.Color(color.FgHiGreen, definedStackNames))
	if stackName, err = shells.PromptUserForInput(prompt, os.Stderr); err != nil {
		return "", runInStackDir, err
	}
	if stackName == "" {
		return "", runInStackDir, fmt.Errorf("stack name is empty")
	}
	if !cfg.Stacks.Has(stackName) {
		return "", runInStackDir, fmt.Errorf("the stack named %s not found", color.HiGreenString(stackName))
	}
	return stackName, runInStackDir, nil
}

func getStack(cfg config.TFWrapperConfig) (stack *config.Stack, runInStackDir bool, err error) {
	if cfg.Stack, runInStackDir, err = chooseStackName(cfg); err != nil {
		return nil, runInStackDir, errors.WithStackTrace(err)
	}

	prompt := fmt.Sprintf("You are about to execute stack name: %s", color.HiGreenString(cfg.Stack))
	if err = shells.PromptUserForYesNo(cfg.NonInteractive, prompt, os.Stderr); err != nil {
		return
	}

	stack, err = cfg.Stacks.Get(cfg.Stack)
	return
}
